package com.alibaba.dingtalk.openapi.helper;

import com.alibaba.dingtalk.openapi.exception.OApiException;
import com.alibaba.dingtalk.openapi.exception.OApiResultException;
import com.alibaba.dingtalk.openapi.utils.DingtalkSignHelper;
import com.alibaba.dingtalk.openapi.utils.HttpHelper;
import com.alibaba.dingtalk.openapi.utils.aes.Utils;
import com.alibaba.dingtalk.openapi.vo.DingtalkConfigVO;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xbongbong.i18n.util.I18nMessageUtil;
import com.xbongbong.paas.redis.PaasRedisHelper;
import com.xbongbong.paas.redis.RedisPrefixConstant;
import com.xbongbong.paas.toolbox.util.CommentUtil;
import com.xbongbong.paas.toolbox.util.StringUtil;
import com.xbongbong.pro.config.ProBaseConfig;
import com.xbongbong.pro.constant.I18nStringConstant;
import com.xbongbong.saas.constant.BasicConstant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.Objects;

/**
 * @author kaka
 */
@Component
public class AuthHelper {

	private static final Logger LOG = LoggerFactory.getLogger(AuthHelper.class);

	@Resource
	private PaasRedisHelper paasRedisHelper;

	/**
	 * 调整到1小时50分钟,10分钟的间隔
 	 */
	public static final long CACHE_TIME = 1000 * 60 * 110L;

	public String getAccessToken(String corpId) throws OApiException {
		return getAccessToken(corpId, false);
	}

	/**
	 * 在此方法中，为了避免频繁获取access_token，
	 * 在距离上一次获取access_token时间在两个小时之内的情况，
	 * 将直接从持久化存储中读取access_token
	 *
	 * 因为access_token和jsapi_ticket的过期时间都是7200秒
	 * 所以在获取access_token的同时也去获取了jsapi_ticket
	 * 注：jsapi_ticket是在前端页面JSAPI做权限验证配置的时候需要使用的
	 * 具体信息请查看开发者文档--权限验证配置
	 * @param corpId 公司corpid
	 * @param force 是否强制刷新accessToken, 正常情况下不要强制刷新
	 * @return accessToken
	 * @throws OApiException 钉钉接口访问异常
	 */
	public String getAccessToken(String corpId, boolean force) throws OApiException {
		long curTime = System.currentTimeMillis();
		String accessTokenJsonStr = paasRedisHelper.getValue("accesstoken", corpId);
		JSONObject accessTokenValue = JSON.parseObject(accessTokenJsonStr);
		String accToken;
		String jsTicket;
		JSONObject jsonTemp = new JSONObject();

		if (force || accessTokenValue == null || curTime - accessTokenValue.getLong("begin_time") >= CACHE_TIME) {
			String timeStamp = String.valueOf(curTime);
			String suiteTicketStr = paasRedisHelper.getSuiteTokenValue(RedisPrefixConstant.DINGTALK_TICKET, "suiteTicket");
			String sign;
			try {
				sign = DingtalkSignHelper.getEncodeSign(suiteTicketStr, timeStamp, ProBaseConfig.getSuiteSecret());
			} catch (Exception e) {
				throw new OApiResultException(I18nMessageUtil.getMessage(I18nStringConstant.SIGNATURE_ERROR));
			}
			String url = Env.OAPI_HOST + "/service/get_corp_token?" + "signature="
					+ sign + "&timestamp=" + timeStamp + "&suiteTicket=" + suiteTicketStr + "&accessKey=" + ProBaseConfig.getSuiteKey();
			JSONObject args = new JSONObject();
			args.put("auth_corpid", corpId);
			JSONObject response = HttpHelper.httpPost(url, args);
			if (response.containsKey("access_token")) {
				accToken = response.getString("access_token");

				// save accessToken
				jsonTemp.clear();
				jsonTemp.put("access_token", accToken);
				jsonTemp.put("begin_time", curTime);

				paasRedisHelper.setValuePermanent("accesstoken", corpId, jsonTemp);
			} else {
				LOG.warn("access_token response == " + response.toJSONString());
				throw new OApiResultException("access_token");
			}

			String urlTicket = Env.OAPI_HOST + "/get_jsapi_ticket?" + "type=jsapi" + "&access_token=" + accToken;
			JSONObject responseTicket = HttpHelper.httpGet(urlTicket);
			if (responseTicket.containsKey("ticket")) {
				jsTicket = responseTicket.getString("ticket");

				// save jsticket
				jsonTemp.clear();
				jsonTemp.put("ticket", jsTicket);
				jsonTemp.put("begin_time", curTime);

				paasRedisHelper.setValuePermanent("jsticket", corpId, jsonTemp);
			} else {
				LOG.warn("responseTicket == " + responseTicket.toJSONString());
				throw new OApiResultException("ticket");
			}

		} else {
			return accessTokenValue.getString("access_token");
		}

		return accToken;
	}

	/**
	 * 正常的情况下，jsapi_ticket的有效期为7200秒，所以开发者需要在某个地方设计一个定时器，定期去更新jsapi_ticket
	 * @param accessToken accessToken
	 * @param corpId 公司corpid
	 * @return jsapiTicket
	 * @throws OApiException 钉钉接口访问异常
	 */
	public String getJsApiTicket(String accessToken, String corpId) throws OApiException {
		String jsTicketStr = paasRedisHelper.getValue("jsticket", corpId);
		JSONObject jsTicketValue = JSON.parseObject(jsTicketStr);
		long curTime = System.currentTimeMillis();
		String jsTicket = "";

		 if (jsTicketValue == null || curTime -
		 jsTicketValue.getLong("begin_time") >= CACHE_TIME) {
			String url = Env.OAPI_HOST + "/get_jsapi_ticket?" + "type=jsapi" + "&access_token=" + accessToken;
			JSONObject response = HttpHelper.httpGet(url);
			if (response.containsKey("ticket")) {
				jsTicket = response.getString("ticket");
				
				JSONObject jsonTemp = new JSONObject();
				jsonTemp.clear();
				jsonTemp.put("ticket", jsTicket);
				jsonTemp.put("begin_time", curTime);

				paasRedisHelper.setValuePermanent("jsticket", corpId, jsonTemp);
				return jsTicket;
			} else {
				throw new OApiResultException("ticket");
			}
		 } else {
			 return jsTicketValue.getString("ticket");
		 }
	}

	/**
	 * 计算钉钉签名
	 */
	public static String sign(String ticket, String nonceStr, long timeStamp, String url) throws OApiException {
		String plain = "jsapi_ticket=" + ticket + "&noncestr=" + nonceStr + "&timestamp=" + String.valueOf(timeStamp)
				+ "&url=" + url;
		try {
			MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
			sha1.reset();
			sha1.update(plain.getBytes("UTF-8"));
			return Utils.byteToHex(sha1.digest());
		} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
			throw new OApiResultException(e.getMessage());
		}
	}

	/**
	 * 获取agentId, 默认调这个方法，先取缓存，若缓存没有则从钉钉接口获取并刷新缓存
	 * @param corpid 公司ID
	 * @param appId 微应用appId
	 * @return 该公司绑定该微应用产生的agentId
	 * @throws OApiException 抛出钉钉接口访问异常
	 */
	public String getAgentId(String corpid, String appId) throws OApiException {
		return getAgentId(corpid, appId, false);
	}

	/**
	 * 获取 agentId
	 * @param corpid 公司corpid
	 * @param appId 应用 appId
	 * @return 该公司绑定该微应用产生的agentId
	 * @throws OApiException 抛出钉钉接口访问异常
	 */
	public String getAgentId(String corpid, String appId, boolean force) throws OApiException {
		//先从缓存中拿，没有再访问接口获取并存到缓存中
		String agentId = null;
		if(!force) {
			agentId = paasRedisHelper.getValue("dingAgentId", corpid);
			if(!StringUtil.isEmpty(agentId)) {
				return agentId;
			}
		}
		String accessToken = paasRedisHelper.getSuiteTokenValue(RedisPrefixConstant.DINGTALK_TICKET, "suiteToken");
		String url = "https://oapi.dingtalk.com/service/get_auth_info?suite_access_token=" + accessToken;
		JSONObject args = new JSONObject();
		String permanentCodeStr = paasRedisHelper.getValue("permanentcode", corpid);
		args.put("suite_key", Env.SUITE_KEY);
		args.put("auth_corpid", corpid);
		args.put("permanent_code", permanentCodeStr);
		LOG.warn("-------args:{},url:{}",args, url);
		JSONObject response = HttpHelper.httpPost(url, args);
		LOG.info("com.alibaba.dingtalk.openapi.helper.AuthHelper.getAgentId="+ JSON.toJSONString(response));
		if (response.containsKey("auth_info")) {
			JSONArray agents = (JSONArray) ((JSONObject) response.get("auth_info")).get("agent");

			for (int i = 0; i < agents.size(); i++) {
				if (((JSONObject) agents.get(i)).get("appid").toString().equals(appId)) {
					agentId = ((JSONObject) agents.get(i)).get("agentid").toString();
					//放入缓存
					paasRedisHelper.setValuePermanent("dingAgentId", corpid, agentId);
					break;
				}
			}
		} else {
			throw new OApiResultException("agentid");
		}
		return agentId;
	}

	/**
	 * 获取 SSO Token
	 */
	public String getSsoToken() throws OApiException {
		String url = "https://oapi.dingtalk.com/sso/gettoken?corpid=" + Env.CORP_ID + "&corpsecret=" + Env.SSO_SECRET;
		JSONObject response = HttpHelper.httpGet(url);
		String ssoToken;
		if (response.containsKey("access_token")) {
			ssoToken = response.getString("access_token");
		} else {
			throw new OApiResultException("Sso_token");
		}
		return ssoToken;
	}

	/**
	 * 计算pageUrl对应的钉钉config信息
	 * @param pageUrl 需要签名的url
	 * @return pageUrl 签名后的数据
	 */
	public DingtalkConfigVO getConfig(HttpServletRequest request, String pageUrl) {
		String queryString = null;
		String[] urlStrings = pageUrl.split("\\?");
		if(urlStrings.length > 1) {
			queryString = urlStrings[1];
			// 参数中有#/index等内容需要过滤掉 2018-07-11 by hongxiao.liu
			String [] hashParams = queryString.split("#");
			queryString = hashParams[0];
		}
		String urlString = urlStrings[0];

		//排除不是本服务的干扰
		String localUrl = Env.DINGTALK_FRONT_URL;
		String localUrlProtocol = null;
		if(localUrl != null ){
			int index = localUrl.indexOf(":");
			int length = localUrl.length();
			// 获取https:// ,或者 http://
			localUrlProtocol = localUrl.substring(0, index+3);
			localUrl = (String)localUrl.subSequence(index+3, length);
		}

		String tmpUrlString = "";
		if(urlString != null ){
			int index = urlString.indexOf(":");
			int length = urlString.length();
			tmpUrlString = (String) urlString.subSequence(index+3, length);
		}

		// TODO 后续校验开启
		LOG.info("tmpUrlString == " + tmpUrlString + ", localUrl == " + localUrl);
//		if(localUrl == null || !tmpUrlString.startsWith(localUrl)) {
//			LOG.error("getConfig 获取的request.getRequestURL().toString() 不是本服务的地址，而是：" +
//					urlString + ", Env.LOCAL_URL=" + Env.LOCAL_URL);
//			return null;
//		}

		Map<String, String> dataMap = CommentUtil.urlToMap("?" + queryString);
		if (dataMap.containsKey("openConversationId")) {
			// url中参数值有==字符，直接使用split有问题，添加limit限制：如果limit > 0，(从左到右)最多分割n-1次，数组的长度不会大于n，结尾的空字符串不会丢弃
			dataMap = CommentUtil.urlToMap("?" + queryString, BasicConstant.TWO);
		}
		String corpId = dataMap.get("corpid");
		String appId = dataMap.get("appid");
		// 酷应用传入群id
		String openConversationId = dataMap.get("openConversationId");
		String source = dataMap.get("source");
		LOG.info("-------------source:{},corpId:{},openConversationId{}",source,corpId,openConversationId);
		//因为在聚石塔里，获取不到https的urlString，参与签名时用localUrl的协议
		urlString = localUrlProtocol + tmpUrlString;
		String queryStringEncode;
		String url;
		if (queryString != null) {
			queryStringEncode = URLDecoder.decode(queryString);
			url = urlString + "?" + queryStringEncode;
		} else {
			url = urlString;
		}

		String nonceStr = "abcdefg";
		long timeStamp = System.currentTimeMillis() / 1000;
		String signedUrl = url;
		String accessToken = null;
		String ticket = null;
		String signature = null;
		String agentid = null;
		try {
			accessToken = getAccessToken(corpId);
			ticket = getJsApiTicket(accessToken, corpId);
			signature = sign(ticket, nonceStr, timeStamp, signedUrl);
			agentid = getAgentId(corpId, appId);
		} catch (OApiException e) {
			LOG.error("getconfig出错", e);
			LOG.error("{jsticket:'" + ticket + "',signature:'" + signature + "',nonceStr:'" + nonceStr + "',timeStamp:'" + timeStamp + "',corpId:'" + corpId + "',agentid:'" + agentid + "',appid:'" + appId + "'}  get date fail" + urlString);
			return null;
		}

		DingtalkConfigVO configVO = new DingtalkConfigVO();
		configVO.setAgentid(agentid);
		configVO.setAppid(appId);
		configVO.setCorpid(corpId);
		configVO.setJsticket(ticket);
		configVO.setTimeStamp(timeStamp);
		configVO.setNonceStr(nonceStr);
		configVO.setSignature(signature);
		if (Objects.nonNull(openConversationId)) {
			configVO.setOpenConversationId(openConversationId);
		}

		return configVO;
	}

	/**
	 * 3: 使用appid及appSecret访问如下接口，获取accesstoken，此处获取的token有效期为2小时，有效期内重复获取，返回相同值，
	 * 并自动续期，如果在有效期外获取会获得新的token值，建议定时获取本token，不需要用户登录时再获取。
	 * @param appid scanAppid
	 * @param appSecret scanAppSecret
	 * @return snsAccesstoken
	 * @throws OApiException 钉钉接口访问异常
	 */
	public String getSNSAccessToken(String appid,String appSecret) throws OApiException {
		long curTime = System.currentTimeMillis();
		JSONObject accessTokenValue = JSON.parseObject(paasRedisHelper.getValue("snsAccesstoken", appid));
		String snsAccesstoken;
		JSONObject jsonTemp = new JSONObject();
		if (accessTokenValue == null || curTime - accessTokenValue.getLong("begin_time") >= CACHE_TIME) {
			String snsUrl = Env.OAPI_HOST + "/sns/gettoken?" + "appid=" + appid + "&appsecret=" + appSecret;
			JSONObject response = HttpHelper.httpGet(snsUrl);
			if (response != null && response.containsKey("access_token")) {
				snsAccesstoken = response.getString("access_token");
				// save accessToken
				jsonTemp.clear();
				jsonTemp.put("snsAccesstoken", snsAccesstoken);
				jsonTemp.put("begin_time", curTime);

				paasRedisHelper.setValuePermanent("snsAccesstoken", appid, jsonTemp);
			} else {
				LOG.error("OApiResultException snsAccesstoken");
				throw new OApiResultException("snsAccesstoken");
			}
			//要解析
		} else {
			return accessTokenValue.getString("snsAccesstoken");
		}
		return snsAccesstoken;
	}

	/**
	 * 5: 在获得钉钉用户的持久授权码后，通过以下接口获取该用户授权的SNS_TOKEN，此token的有效时间为2小时，重复获取不会续期。
	 * @param openid
	 * @param persistenCode
	 * @return
	 * @throws OApiException
	 */
	public String getSNSToken(String snsAccesstoken, String openid, String persistenCode) throws OApiException {
		long curTime = System.currentTimeMillis();
//		JSONObject snsTokenValue = JSON.parseObject(paasRedisHelper.getValue("snsToken", openid));
		String snsToken;
		JSONObject jsonTemp = new JSONObject();
//		if (snsTokenValue == null || curTime - snsTokenValue.getLong("begin_time") >= CACHE_TIME) {
			String url = Env.OAPI_HOST + "/sns/get_sns_token?" + "access_token=" + snsAccesstoken;

			JSONObject args = new JSONObject();
			args.put("openid", openid);
			args.put("persistent_code",persistenCode);
			JSONObject response = HttpHelper.httpPost(url, args);
			if (response != null && response.containsKey("sns_token")) {
				snsToken = response.getString("sns_token");
				jsonTemp.put("expires_in", response.getString("expires_in"));
				jsonTemp.put("begin_time", curTime);
				jsonTemp.put("sns_token", snsToken);
//				paasRedisHelper.setValuePermanent("snsToken", openid, jsonTemp);
			} else {
				throw new OApiResultException("SNSToken");
			}
//		} else {
//			//如果超时
//			return snsTokenValue.getString("snsToken");
//		}
		return snsToken;
	}
}
